unit smSceneManager;

interface

uses
  smSceneRender, smCommonTypes, smExceptions, SysUtils;

type
  TSceneManager = class;

  TSceneType = (
    stNone  = 0,
    stIntro = 1,
    stMenu  = 2,
    stLevel = 4,
    stOutro = 8
  );
  TSceneState = (ssNone, ssLoading, ssReady, ssUnloading);
  TScene = class sealed(TObject)
  protected
    FOwner: TSceneManager;
    FSystemName: AnsiString;
    FSystemType: TSceneType;
    FDisplayName: AnsiString;
    FState: TSceneState;
    FAlpha: Single;

  public
    property Owner: TSceneManager read FOwner;
    property SystemName: AnsiString read FSystemName;
    property SystemType: TSceneType read FSystemType;
    property DisplayName: AnsiString read FDisplayName write FDisplayName;
    property State: TSceneState read FState;
    property Alpha: Single read FAlpha write FAlpha;

    constructor Create(const AOwner: TSceneManager); reintroduce;
  end;

  TSceneManager = class sealed(TObject)
  protected
    FScenes: TGameList;
    FCurrentScene: TScene;
    FNextScene: TScene;
    FRenderer: TSceneRenderer;

    function AddStumpScene(): TScene;
    function GetScene(const Name: AnsiString): TScene;
    function ProcessFade(): Boolean;

  public
    property Scenes[const Name: AnsiString]: TScene read GetScene; default;
    property SceneList: TGameList read FScenes;
    property CurrentScene: TScene read FCurrentScene;
    property NextScene: TScene read FNextScene;
    property Renderer: TSceneRenderer read FRenderer;

    function AddScene(const Name: AnsiString; const AType: TSceneType): TScene;
    procedure SetNextScene(const Name: AnsiString);
    procedure Update();
    procedure Render();
    procedure LoadScene();
    procedure UnloadScene(const AFinal: Boolean; const AQuit: Boolean = False);

    constructor Create();
    destructor Destroy(); override;
  end;

var
  SceneManager: TSceneManager;

implementation

uses
  smGUI, smLuaUtils, smPlayer;

{ TSceneManager }

function TSceneManager.AddStumpScene;
begin
  Result := AddScene('stump_scene', stNone);
  Result.FState := ssReady;
end;

constructor TSceneManager.Create;
begin
  FScenes := TGameList.Create();
  FScenes.ObjectElements := True;

  FCurrentScene := AddStumpScene();
  FNextScene    := FCurrentScene;

  FRenderer := TSceneRenderer.Create();
end;

function TSceneManager.GetScene;
var
  I: Integer;
begin
  for I := 0 to FScenes.Count - 1 do
{$R-}
    if TScene(FScenes[I]).SystemName = Name then
    begin
      Result := FScenes[I];
      Exit;
    end;
{$R+}
  Result := nil;
end;

function TSceneManager.AddScene;
begin
  Result := TScene.Create(Self);

  Result.FSystemName := Name;
  Result.FSystemType := AType;
  Result.DisplayName := AnsiString(Format('<%s>', [String(Name)]));
end;

procedure TSceneManager.SetNextScene;
begin
  if (CurrentScene.State = ssReady) and (Name <> CurrentScene.SystemName) then
  begin
    FNextScene := GetScene(Name);

    if Assigned(FNextScene) then

    else
    begin
{$R-}
      FNextScene := FScenes[0];
{$R+}
      raise EScenesNotFound.Create(Name);
    end;
  end;
end;

procedure TSceneManager.LoadScene;
begin
  CurrentScene.Alpha := 1.0; // start from full visibility
  CurrentScene.FState := ssLoading;
  // Before the very scene load
  if  (CurrentScene.SystemType = stNone)
      or
      ( (CurrentScene.SystemType > stNone)
        and (RunInitFile(CurrentScene.SystemName))
      )
  then
    ProcessFade()
  else
    CurrentScene.FState := ssNone;
end;

procedure TSceneManager.UnloadScene;
begin
  if AFinal then
  begin
    // todo: actual unload scene
    // Clear resources
    if FCurrentScene.SystemType <> stNone then
    begin
      StopInitFile();
    end;

    if not AQuit then
    begin
      FCurrentScene := NextScene;
      GUIManager.Dispatch(gmSceneChanged, Byte(FCurrentScene.SystemType), 0);
      LoadScene();
    end;
  end
  else
  begin
    CurrentScene.Alpha := 0.0; // start from full transparency
    CurrentScene.FState := ssUnloading;
    ProcessFade();
  end;
end;

function TSceneManager.ProcessFade;
begin
  if CurrentScene.State = ssUnloading then
    if CurrentScene.Alpha < 0.95 then
    begin
      CurrentScene.Alpha := CurrentScene.Alpha + 0.02;
      Result := True;
    end
    else
    begin
      CurrentScene.Alpha := 1.0;      // adjust
      Result := False;
    end

  else if CurrentScene.State = ssLoading then
    if CurrentScene.Alpha > 0.05 then
    begin
      CurrentScene.Alpha := CurrentScene.Alpha - 0.02;
      Result := True;
    end
    else
    begin
      CurrentScene.Alpha := 0.0;      // adjust
      Result := False
    end

  else
    Result := False;
end;

procedure TSceneManager.Update;
begin
  if CurrentScene <> NextScene then
  begin
    // Fade out
    if CurrentScene.SystemType = stNone then
      UnloadScene(True)
    else
      if CurrentScene.State = ssReady then
      begin
        UnloadScene(False);       // step 1
        Exit;
      end
      else
        if ProcessFade() then
          Exit                    // step 2
        else
          UnloadScene(True);      // final step 3

  end
  else
    if CurrentScene.State = ssLoading then
      if ProcessFade() then
        Exit
      else
        CurrentScene.FState := ssReady

    else if CurrentScene.State = ssReady then
    begin
      case CurrentScene.SystemType of
        stLevel:
          if Assigned(Player.World) then
            Player.World.Update();
      end;
    end;
end;

procedure TSceneManager.Render;
begin
  case CurrentScene.SystemType of
    stIntro: Renderer.DrawSceneIntro(CurrentScene);
    stMenu : Renderer.DrawSceneMenu(CurrentScene);
    stLevel: Renderer.DrawSceneLevel(CurrentScene);
    stOutro: Renderer.DrawSceneOutro(CurrentScene);

  else
    Renderer.DrawSceneNone(CurrentScene);

  end;
end;

destructor TSceneManager.Destroy;
begin
  FScenes.Free();
  FRenderer.Free();
  inherited;
end;

{ TScene }

constructor TScene.Create;
begin
  inherited Create();
  FOwner := AOwner;
  Owner.SceneList.Add(Self);

  FAlpha := 0.0;
  FState := ssNone;
end;

end.
